﻿namespace UnityEngine.Core
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using Microsoft.Xna.Framework;
    using UnityEngine.Components;
    using System.Xml.Serialization;
    using System.Xml.Schema;
    using System.Xml;
    using System.IO;
    using Ionic.Zip;
    using System.CodeDom.Compiler;
    using Microsoft.CSharp;
    using UnityEngine.IO;
    using System.Diagnostics;
    using System.Text;

    public class GameObject
    {
        private static Assembly runtimeAssemble = null;

        internal static Dictionary<string, Type> AvailableComponents = new Dictionary<string, Type>();

        private readonly Dictionary<string, Component> _Components = new Dictionary<string, Component>();

        private readonly Dictionary<Type, Component> _ComponentInChildren = new Dictionary<Type, Component>();

        private readonly Dictionary<Type, List<Component>> _ComponentsInChildren = new Dictionary<Type, List<Component>>();

        private readonly Dictionary<Type, Component> _Parents = new Dictionary<Type, Component>();

        private readonly Dictionary<string, GameObject> _GameObjectParent = new Dictionary<string, GameObject>();

        private readonly Dictionary<string, GameObject> _GameObjectChildren = new Dictionary<string, GameObject>();

        public static readonly string GameSceneFileSuffix = "dotaX";

        public static readonly string ComponentsFileSuffix = "xml";

        public static readonly string GameObjectFileSuffix = "prefab";

        public bool IsDestroyed
        {
            get;
            set;
        }

        public GameObject()
        {
            this.Name = "GameObject";
            this.IsFromPrefab = false;
            this.IsDestroyed = false;
        }

        public GameObject(string name)
        {
            this.Name = name;
        }

        #region Path
        private string prefabPath;
        public string PrefabPath
        {
            get
            {
                if (HasPrefab)
                    return this.GetComponent<Prefab>().Path;
                return null;
            }
            set
            {
                if (HasPrefab)
                    this.GetComponent<Prefab>().Path = value;
                else
                    this.AddComponent(new Prefab());
                this.GetComponent<Prefab>().Path = value;
            }
        }

        public bool HasPrefab
        {
            get
            {
                var o = this.GetComponent<Prefab>();
                return o != null && o.Path != null && o.Path.Length > 0;
            }
        }
        #endregion

        private GameScene gameScene = null;
        public GameScene GameScene
        {
            get
            {
                if (gameScene == null)
                {
                    GameObject o = this;
                    while (o is GameScene == false)
                        o = o.Transform.Parent.GameObject;
                    gameScene = o as GameScene;
                }

                return gameScene;
            }
        }

        public GameObject[] Children
        {
            get
            {
                if (this.Transform == null)
                    return new GameObject[0];

                List<GameObject> gameObjects = new List<GameObject>();
                foreach (Transform child in this.Transform.Children)
                    gameObjects.Add(child.GameObject);
                return gameObjects.ToArray();
            }
        }

        public Component[] Components
        {
            get
            {
                Component[] values = new Component[this._Components.Count];
                _Components.Values.CopyTo(values, 0);
                return values;
            }
        }

        public string Name
        {
            set;
            get;
        }

        public Renderer Renderer
        {
            get
            {
                Renderer ret = GetComponent<Renderer>();
                if (ret == null)
                {
                    ret = new Renderer(this);
                    this.AddComponent(ret);
                }
                return ret;
            }
            set
            {
                this.RemoveComponent<Renderer>();
                if (value != null)
                    this.AddComponent(value);
            }
        }

        public Transform Transform
        {
            get
            {
                Transform ret = GetComponent<Transform>();
                if (ret == null)
                {
                    ret = new Transform(this);
                    this.AddComponent(ret);
                }
                return ret;
            }
            set
            {
                this.RemoveComponent<Transform>();
                if (value != null)
                    this.AddComponent(value);
            }
        }

        public bool IsFromPrefab
        {
            get;
            set;
        }

        public void AddChild(GameObject child, int index = -1)
        {
            if (index < 0 || index > this.Transform.Children.Count)
                this.Transform.Children.Add(child.Transform);
            else
                this.Transform.Children.Insert(index, child.Transform);
            child.Transform.Parent = this.Transform;
            foreach (Component c in child.Components)
                this.ClearFromParent(c);
        }

        public void RemoveChild(GameObject child)
        {
            foreach (Component c in child.Components)
                this.ClearFromParent();
            child.Destroy(null);
            child.IsDestroyed = true;
            this.Transform.Children.Remove(child.Transform);
        }

        private Assembly CompiledScripts()
        {
            return null;

            if (runtimeAssemble != null)
                return runtimeAssemble;

            CodeDomProvider provider = new CSharpCodeProvider();
            CompilerParameters cp = new CompilerParameters();
            cp.CompilerOptions = "/target:library";
            cp.GenerateExecutable = false;
            cp.GenerateInMemory = true;
            cp.IncludeDebugInformation = true;
            cp.OutputAssembly = "UnityDotaX";
            cp.ReferencedAssemblies.Add("UnityEditor.exe");
            cp.ReferencedAssemblies.Add("UnityEngine.exe");
            cp.ReferencedAssemblies.Add("System.dll");
            cp.ReferencedAssemblies.Add("System.Core.dll");
            cp.ReferencedAssemblies.Add("System.Data.dll");
            cp.ReferencedAssemblies.Add("System.Xml.dll");
            cp.ReferencedAssemblies.Add("System.Xml.Linq.dll");
            cp.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
            cp.ReferencedAssemblies.Add("MdxLib.dll");
            cp.ReferencedAssemblies.Add(Environment.GetEnvironmentVariable("XNAGSv4") + @"\References\Windows\x86\Microsoft.Xna.Framework.dll");
            cp.ReferencedAssemblies.Add(Environment.GetEnvironmentVariable("XNAGSv4") + @"\References\Windows\x86\Microsoft.Xna.Framework.Game.dll");
            cp.ReferencedAssemblies.Add(Environment.GetEnvironmentVariable("XNAGSv4") + @"\References\Windows\x86\Microsoft.Xna.Framework.Graphics.dll");
            string[] files = System.IO.Directory.GetFiles(IOManager.Instance.AssetsFolder, "*.cs", SearchOption.AllDirectories);
            CompilerResults cr = provider.CompileAssemblyFromFile(cp, files);
            if (!cr.Errors.HasErrors)
            {
                runtimeAssemble = cr.CompiledAssembly;
                return cr.CompiledAssembly;
            }
            else
            {
                throw new Exception("Compile incorrectly.");
            }
        }

        public bool IsAvailableComponent(string className)
        {
            if (_Components.ContainsKey(className))
                return false;

            if (AvailableComponents.ContainsKey(className))
                return true;

            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            Assembly assembly = this.CompiledScripts();
            if (assembly != null)
            {
                List<Assembly> list = new List<Assembly>(assemblies);
                list.Add(assembly);
                assemblies = list.ToArray();
            }

            for (int i = 0; i < assemblies.Length; i++)
            {
                assembly = assemblies[i];

                if (assembly.FullName.Contains("Unity") == false)
                    continue;

                Type[] types = assembly.GetTypes();
                Type type = null;
                for (int index = 0; index < types.Length; index++)
                {
                    if (types[index].Name.CompareTo(className) == 0)
                    {
                        type = types[index];
                        break;
                    }
                }
                if (type != null)
                {
                    AvailableComponents.Add(className, type);
                    return true;
                }
            }

            return false;
        }

        public Type GetType(string className)
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            Assembly assembly = this.CompiledScripts();
            if (assembly != null)
            {
                List<Assembly> list = new List<Assembly>(assemblies);
                list.Add(assembly);
                assemblies = list.ToArray();
            }

            for (int i = 0; i < assemblies.Length; i++)
            {
                assembly = assemblies[i];

                if (assembly.FullName.Contains("Unity") == false)
                    continue;

                Type[] types = assembly.GetTypes();
                Type type = null;
                for (int index = 0; index < types.Length; index++)
                {
                    if (types[index].Name.CompareTo(className) == 0)
                    {
                        type = types[index];
                        break;
                    }
                }
                if (type != null)
                {
                    return type;
                }
            }

            return null;
        }

        public dynamic AddComponent(string className)
        {
            if (_Components.ContainsKey(className))
                throw new Exception("Game object already has the component :" + className);

            Component component = null;
            Type type = GetType(className);
            if (type != null)
            {
                this.ClearFastRelation();
                component = Activator.CreateInstance(type) as Component;
                component.GameObject = this;
                this._Components.Add(className, component);
                return component;
            }

            return component;
        }

        public void AddComponent(Component component)
        {
            if (component != null)
            {
                this.ClearFastRelation();
                _Components.Add(component.GetType().Name, component);
            }
        }

        public bool HasComponent(string name)
        {
            return _Components.ContainsKey(name);
        }

        public List<Component> CollectActiveComponent()
        {
            List<Component> ret = new List<Component>();

            if (this.Transform.GameObjectActive == false)
                return ret;

            foreach (Component component in this._Components.Values)
            {
                ret.Add(component);
            }

            foreach (Transform transform in this.Transform.Children)
            {
                foreach (var c in transform.GameObject.CollectActiveComponent())
                    ret.Add(c);
            }

            return ret;
        }

        private Dictionary<int, List<Component>> mergeSortedList = new Dictionary<int, List<Component>>();
        public List<Component> MergeBlockSort(List<Component> list, List<int> blockBegin, List<int> blockEnd, int block_f, int block_r, int depth)
        {
            if (block_f == block_r)
                return list;

            if (mergeSortedList.ContainsKey(depth) == false)
                mergeSortedList.Add(depth, new List<Component>());
            List<Component> ret = mergeSortedList[depth];
            ret.Clear();

            // define sorted parameters
            int mid = (block_f + block_r) / 2;
            int left = blockBegin[block_f];
            int leftEnd = blockEnd[mid];
            int right = blockBegin[mid + 1];
            int rightEnd = blockEnd[block_r];

            // block_f ... mid  merge with mid + 1 ... block_r
            if (block_f < mid)
            {
                List<Component> leftList = MergeBlockSort(list, blockBegin, blockEnd, block_f, mid, depth + 1);
                for (int i = 0; i < leftList.Count; i++)
                    list[left + i] = leftList[i];
            }
            if (mid + 1 < block_r)
            {
                List<Component> rightList = MergeBlockSort(list, blockBegin, blockEnd, mid + 1, block_r, depth + 1);
                for (int i = 0; i < rightList.Count; i++)
                    list[right + i] = rightList[i];
                rightList.Clear();
            }

            // merge them
            while (left <= leftEnd && right <= rightEnd)
                if (list[left].Priority >= list[right].Priority)
                    ret.Add(list[left++]);
                else
                    ret.Add(list[right++]);
            while (left <= leftEnd)
                ret.Add(list[left++]);
            while (right <= rightEnd)
                ret.Add(list[right++]);

            //for (int i = blockBegin[block_f]; i < blockEnd[block_r]; i++)
            //    for (int j = i + 1; j < blockEnd[block_r]; j++)
            //        if (ret[i].Priority < ret[j].Priority)
            //            throw new Exception("Sorted incorrectly.");

            return ret;
        }

        protected Component[] GetSortedComponents()
        {
            List<Component> all = CollectActiveComponent();
            List<int> blockBegin = new List<int>();
            List<int> blockEnd = new List<int>();
            blockBegin.Add(0);
            for (int i = 0; i < all.Count - 1; i++)
                if (all[i].Priority < all[i + 1].Priority)
                {
                    blockEnd.Add(i);
                    blockBegin.Add(i + 1);
                }
            blockEnd.Add(all.Count - 1);
            Component[] ret = MergeBlockSort(all, blockBegin, blockEnd, 0, blockBegin.Count - 1, 0).ToArray();

            //for (int i = 0; i < ret.Length; i++)
            //    for (int j = i + 1; j < ret.Length; j++)
            //        if (ret[i].Priority < ret[j].Priority)
            //            throw new Exception("Sorted incorrectly.");
            return ret;
        }

        public virtual void Startup(GameTime gameTime)
        {
            Component[] all = this.GetSortedComponents();

            // update normal
            for (int i = 0; i < all.Length; i++)
                all[i].Startup(gameTime);
        }

        public virtual void Draw(GameTime gameTime)
        {
            Component[] all = this.GetSortedComponents();

            // update normal
            for (int i = 0; i < all.Length; i++)
                all[i].Draw(gameTime);
        }

        public virtual void Update(GameTime gameTime)
        {
            Component[] all = this.GetSortedComponents();

            // update normal
            for (int i = 0; i < all.Length; i++)
                all[i].Update(gameTime);
        }

        public virtual void Destroy(GameTime gameTime)
        {
            Component[] all = this.GetSortedComponents();

            // update normal
            for (int i = 0; i < all.Length; i++)
                all[i].Destroy(gameTime);
        }

        public virtual void ClearFromParent(Component c = null)
        {
            GameObject current = this;
            while (current.Transform.Parent != null)
            {
                current = current.Transform.Parent.GameObject;
                current.ClearFastRelation(c);
            }
            this.ClearFastRelationInChildren(c);
        }

        public virtual void ClearFastRelation(Component c = null)
        {
            if (c == null)
            {
                _ComponentInChildren.Clear();
                _ComponentsInChildren.Clear();
                _Parents.Clear();
            }
            else
            {
                Type cType = c.GetType();
                if (_ComponentInChildren.ContainsKey(cType)) _ComponentInChildren.Remove(cType);
                if (_ComponentsInChildren.ContainsKey(cType)) _ComponentsInChildren.Remove(cType);
                if (_Parents.ContainsKey(cType)) _Parents.Remove(cType);
            }

            _GameObjectChildren.Clear();
            _GameObjectParent.Clear();
        }

        public void ClearFastRelationInChildren(Component c = null)
        {
            ClearFastRelation(c);

            foreach (Transform transform in this.Transform.Children)
            {
                transform.GameObject.ClearFastRelationInChildren(c);
            }
        }

        public GameObject GetGameObjectInParent(string name)
        {
            if (this.Transform.GameObjectActive == false)
                return null;

            if (_GameObjectParent.ContainsKey(name))
                return _GameObjectParent[name];

            GameObject current = this;
            while (current != null && current.Name.EndsWith(name) == false)
                current = current.Transform.Parent.GameObject;

            _GameObjectParent.Add(name, current);
            return current;
        }

        public GameObject GetGameObjectInChildren(string name, bool allowDeactiveObject = false)
        {
            if (this.Transform.GameObjectActive == false && allowDeactiveObject == false)
                return null;

            if (_GameObjectChildren.ContainsKey(name))
                return _GameObjectChildren[name];

            GameObject ret = null;
            if (name == this.Name)
                ret = this;
            else
            {
                foreach (var child in this.Transform.Children)
                {
                    ret = child.GameObject.GetGameObjectInChildren(name, allowDeactiveObject);
                    if (ret != null)
                        break;
                }
            }

            _GameObjectChildren.Add(name, ret);
            return ret;
        }

        public T GetParent<T>() where T : Component
        {
            if (this.Transform.GameObjectActive == false)
                return null;

            if (this._Parents.ContainsKey(typeof(T)))
            {
                return this._Parents[typeof(T)] as T;
            }

            GameObject current = null;
            if (this.Transform.Parent != null)
                current = this.Transform.Parent.GameObject;
            if (current == null)
            {
                this._Parents.Add(typeof(T), null);
            }
            else
            {
                if (current.GetComponent<T>() != null)
                    this._Parents.Add(typeof(T), current.GetComponent<T>());
                else
                    this._Parents.Add(typeof(T), current.GetParent<T>());
            }

            return this._Parents[typeof(T)] as T;
        }

        public T GetComponent<T>() where T : Component
        {
            return GetComponent(typeof(T).Name) as T;
        }

        public dynamic GetComponent(string className)
        {
            if (_Components.ContainsKey(className))
                return _Components[className];
            else
            {
                Type type = this.GetType(className);
                if (type != null)
                {

                    foreach (Component c in _Components.Values)
                        if (type.IsInstanceOfType(c))
                            return c;
                }

                return null;
            }
        }

        public T GetComponentInChildren<T>() where T : Component
        {
            if (this.Transform.GameObjectActive == false)
                return null;

            if (_ComponentInChildren.ContainsKey(typeof(T)))
                return _ComponentInChildren[typeof(T)] as T;

            T[] ret = GetComponentsInChildren<T>();
            if (ret != null && ret.Length > 0)
                _ComponentInChildren.Add(typeof(T), ret[0]);
            else
                _ComponentInChildren.Add(typeof(T), null);
            return _ComponentInChildren[typeof(T)] as T;
        }

        public T[] GetComponentsInChildren<T>() where T : UnityEngine.Core.Component
        {
            List<Component> list;
            List<T> result = new List<T>();
            if (this.Transform.GameObjectActive == false)
                return result.ToArray();

            if (_ComponentsInChildren.ContainsKey(typeof(T)))
            {
                list = _ComponentsInChildren[typeof(T)];
                result = new List<T>();
                for (int i = 0; i < list.Count; i++)
                    result.Add(list[i] as T);
                return (T[])result.ToArray();
            }

            list = new List<Component>();
            T single = this.GetComponent<T>();
            if (single != null)
                list.Add(single);

            foreach (var child in this.Transform.Children)
            {
                foreach (var t in child.GameObject.GetComponentsInChildren<T>())
                    list.Add(t);
            }

            _ComponentsInChildren.Add(typeof(T), list);

            result = new List<T>();
            for (int i = 0; i < list.Count; i++)
                result.Add(list[i] as T);
            return (T[])result.ToArray();
        }

        public void RemoveComponent<T>() where T : UnityEngine.Core.Component
        {
            RemoveComponent(typeof(T).Name);
        }

        public void RemoveComponent(string className)
        {
            if (_Components.ContainsKey(className))
            {
                this.ClearFastRelation();
                _Components.Remove(className);
            }
        }

        public virtual XmlSchema GetSchema()
        {
            return null;
        }

        public virtual void ReadXml(XmlNode root)
        {
            this._Components.Clear();

            XmlNode componentsNode = root.SelectSingleNode("./" + "Components");

            int count = int.Parse(componentsNode.Attributes["Count"].Value);
            for (int i = 0; i < count; i++)
            {
                XmlNode node = componentsNode.ChildNodes[i];
                string typeName = node.Attributes["TypeName"].Value;
                Component component = this.AddComponent(typeName) as Component;
                if (component != null)
                    component.ReadXml(node);
            }
        }

        public virtual void WriteXml(XmlWriter writer)
        {
            writer.WriteStartElement("Components");

            writer.WriteStartAttribute("Count");
            writer.WriteValue(this._Components.Count);
            writer.WriteEndAttribute();

            foreach (Component component in this.Components)
            {
                writer.WriteStartElement("Component");

                writer.WriteStartAttribute("TypeName");
                writer.WriteValue(component.GetType().Name);
                writer.WriteEndAttribute();

                component.WriteXml(writer);

                writer.WriteFullEndElement();
            }
            writer.WriteFullEndElement();
        }

        public GameObject Copy()
        {
            GameObject ret = new GameObject(this.Name);
            ZipFile zip = this.ToZipFile();
            using (MemoryStream memory = new MemoryStream())
            {
                zip.Save(memory);
                ret.LoadMemoryStream(memory);
                memory.Close();
            }
            ret.IsFromPrefab = true;
            return ret;
        }

        public ZipFile ToZipFile()
        {
            MemoryStream memoeryStream = new MemoryStream();

            using (ZipFile zipFile = new ZipFile(Encoding.ASCII))
            {
                string folder = this.GetType().Name;

                using (MemoryStream componentStream = new MemoryStream())
                {
                    using (XmlWriter writer = XmlWriter.Create(componentStream))
                    {
                        this.WriteXml(writer);
                        writer.Flush();
                        zipFile.AddEntry(folder + "." + GameObject.ComponentsFileSuffix, componentStream.ToArray());
                        writer.Close();
                    }

                    componentStream.Close();
                }

                int index = 0;
                foreach (GameObject childObject in this.Children)
                {
                    if (childObject.IsFromPrefab == true)
                        continue;

                    index++;
                    using (MemoryStream childStream = new MemoryStream())
                    {
                        ZipFile childZipFile = childObject.ToZipFile();
                        childZipFile.Save(childStream);
                        string name = childObject.Name + "#" + index.ToString();
                        zipFile.AddEntry(name + "." + GameObject.GameSceneFileSuffix, childStream.ToArray());
                        childStream.Close();
                    }
                }

                return zipFile;
            }
        }

        public void LoadMemoryStream(MemoryStream memoryStream)
        {
            memoryStream.Position = 0;
            ReadOptions option = new ReadOptions();
            option.Encoding = Encoding.ASCII;
            ZipFile zipFile = ZipFile.Read(memoryStream, option);
            ICollection<ZipEntry> entries = zipFile.Entries;

            this.Transform.Children.Clear();
            this._Components.Clear();

            foreach (ZipEntry entry in entries)
            {
                if (entry.FileName.EndsWith(GameObject.ComponentsFileSuffix) == true)
                {
                    using (MemoryStream memory = new MemoryStream())
                    {
                        entry.Extract(memory);
                        memory.Position = 0;

                        XmlDocument xmlDoc = new XmlDocument();
                        xmlDoc.Load(memory);
                        this.ReadXml(xmlDoc);
                        memory.Close();
                    }
                }
            }

            foreach (ZipEntry entry in entries)
            {
                if (entry.FileName.EndsWith(GameSceneFileSuffix) == true)
                {
                    using (MemoryStream memory = new MemoryStream())
                    {
                        entry.Extract(memory);

                        GameObject child = new GameObject();
                        string name = entry.FileName;
                        if (name.Contains("#"))
                            name = name.Remove(name.LastIndexOf("#"));
                        child.Name = System.IO.Path.GetFileNameWithoutExtension(name);
                        child.LoadMemoryStream(memory);
                        this.AddChild(child);
                        memory.Close();
                    }
                }
            }
        }
    }
}